/**
 * @Author       : Mo 2427995270@qq.com
 * @Date         : 2025-03-29 15:47:08
 * @LastEditTime : 2025-08-22 18:58:52
 * @FilePath     : \GD32F407-DEMO\hal_driver\hal_i2c_slave.c
 * @Description  : 由外部中断驱动的软件模拟i2c从机引擎
 * @Version      : V1.0
 * @History      :
 * @Note         :
 */
/***************************************Includes***********************************/
#include "hal_i2c_slave.h"


/************************************Private Macros********************************/
// #define


/**********************************Private Statement*******************************/

static inline void i2c_slave_ack(i2c_slave_dev_t *dev);
static inline int i2c_slave_wait_ack(i2c_slave_dev_t *dev);
static void exti_scl_cb(exti_ll_t *exti);
static void exti_sda_cb(exti_ll_t *exti);

/***********************************Public Variables*******************************/



/**********************************Private Variables*******************************/



/**********************************Public Functions********************************/

// 使用scl sda外部中断驱动， 在sda中断中捕获起始和结束信号，在scl里捕获数据等。
// 要有两个中断服务函数，分别是sda和scl中断的。

/**
 * @brief i2c从机设备初始化
 * @param  dev
 * @param  dev_addr         设备地址 7位 不含读写位
 * @param  delay_us         微秒延时回调
 * @param  config           i2c设备配置
 * @return int 0成功 -1失败
 * @note
 */
int i2c_slave_dev_init(i2c_slave_dev_t *dev, int dev_addr, i2c_slave_config_t *config) {
    dev->dev_addr = dev_addr;
    dev->dev_addr_mask = dev_addr << 1;
    dev->receive_buf = config->receive_buf;
    dev->receive_buf_size = config->receive_buf_size;
    dev->receive_cb = config->receive_cb;
    dev->request_cb = config->request_cb;
    dev->arg = config->arg;

    exti_ll_config_t cfg;
    cfg.port = config->port_scl;
    cfg.pinnum = config->pinnum_scl;
    cfg.pull = GPIO_LL_PULL_NO;
    cfg.speed = GPIO_LL_SPEED_HIGH;
    cfg.trigger = EXTI_LL_TRIGGER_RISING_FALLING;
    cfg.cfg_interrupt_priority = config->cfg_interrupt_priority;
    exti_ll_init(&dev->exti_scl, &cfg, exti_scl_cb, dev);
    cfg.port = config->port_sda;
    cfg.pinnum = config->pinnum_sda;
    exti_ll_init(&dev->exti_sda, &cfg, exti_sda_cb, dev);

    dev->__status |= gpio_ll_get_input(&exti_ll_get_gpio_ll(&dev->exti_scl)) ? I2C_SLAVE_STATUS_SCL : 0;
    dev->__status |= gpio_ll_get_input(&exti_ll_get_gpio_ll(&dev->exti_sda)) ? I2C_SLAVE_STATUS_SDA : 0;
    return 0;
}

/**
 * @brief i2c从机 SCL脚驱动服务函数 电平变化时调用
 * @param  dev
 * @note 外部中断 跳变沿触发
 */
static inline void i2c_slave_scl_service(i2c_slave_dev_t *dev) {

    if (gpio_ll_get_input(&exti_ll_get_gpio_ll(&dev->exti_scl))) {
        dev->__status |= I2C_SLAVE_STATUS_SCL;
        // 开始传输
        if (dev->__status & I2C_SLAVE_STATUS_ACCEPT) {
            dev->transfer_cnt++;
            if (dev->transfer_addr & 0x01) {       // 读
                if (dev->transfer_cnt == 9) {      // 检查主机应答
                    if (i2c_slave_wait_ack(dev)) { // 主机未应答
                        dev->__status &= ~I2C_SLAVE_STATUS_ACCEPT;
                    } else {
                        // 主机还想读取，但数据已全部发送，重新获取数据
                        if (dev->transfer_len == dev->request_data.len) {
                            if (dev->request_cb(dev, &dev->request_data, dev->arg) < 0) {
                                dev->__status &= ~I2C_SLAVE_STATUS_START; // 清除起始信号
                                dev->__status &= ~I2C_SLAVE_STATUS_ACCEPT;
                            }
                            dev->transfer_len = 0;
                        }
                    }
                }
                // 已读取完8bit
                if ((dev->transfer_cnt & 0x07) == 0) {
                    dev->transfer_len++;
                }
            } else { // 写
                if ((dev->transfer_len < dev->receive_buf_size) && (dev->transfer_cnt <= 8)) {
                    dev->receive_buf[dev->transfer_len] <<= 1;
                    dev->receive_buf[dev->transfer_len] |= dev->__status & I2C_SLAVE_STATUS_SDA;
                }
                if ((dev->transfer_cnt & 0x07) == 0) { // %8
                    dev->__status |= I2C_SLAVE_STATUS_ACK;
                    dev->transfer_len++;
                }
            }
        } else if (dev->__status & I2C_SLAVE_STATUS_START) { // 检查起始信号
            dev->transfer_cnt++;
            // 处理地址和应答
            if (dev->transfer_cnt <= 8) {
                dev->transfer_addr <<= 1;
                dev->transfer_addr |= dev->__status & I2C_SLAVE_STATUS_SDA;
            }
            if ((dev->transfer_cnt & 0x07) == 0) {
                if ((dev->transfer_addr & dev->dev_addr_mask) != dev->dev_addr_mask) {
                    dev->__status &= ~(I2C_SLAVE_STATUS_START | I2C_SLAVE_STATUS_ACCEPT); // 设备地址不对停止传输
                } else {
                    dev->__status |= I2C_SLAVE_STATUS_ACK; // 接受传输请求
                }
                return;
            }
        }

    } else {
        dev->__status &= ~I2C_SLAVE_STATUS_SCL;
        if ((dev->__status & I2C_SLAVE_STATUS_ACK)) { // 需要应答 第8个下降沿
            if (dev->transfer_addr & 0x01) {          // 准备要读取的数据
                if (dev->request_cb(dev, &dev->request_data, dev->arg) < 0) {
                    dev->__status &= ~I2C_SLAVE_STATUS_START; // 清除起始信号
                    dev->__status &= ~I2C_SLAVE_STATUS_ACCEPT;
                }
            }
            i2c_slave_ack(dev);
            dev->__status &= ~I2C_SLAVE_STATUS_ACK;
        }
        if (dev->transfer_cnt == 9) {
            if (dev->__status & I2C_SLAVE_STATUS_START) {
                dev->__status &= ~I2C_SLAVE_STATUS_START;
                dev->__status |= I2C_SLAVE_STATUS_ACCEPT;
            }
            gpio_ll_set_mode(&exti_ll_get_gpio_ll(&dev->exti_sda), GPIO_LL_MODE_INPUT);
            dev->transfer_cnt = 0;
        }
        // 接受请求同时是读取，在SCL下降沿设置bit
        if ((dev->__status & I2C_SLAVE_STATUS_ACCEPT) && (dev->transfer_addr & 0x01)) {
            if (dev->transfer_cnt < 8) {
                gpio_ll_set_mode(&exti_ll_get_gpio_ll(&dev->exti_sda), GPIO_LL_MODE_OUTPUT);
                int bit = dev->request_data.data[dev->transfer_len] & (0x80 >> dev->transfer_cnt) ? 1 : 0;
                gpio_ll_set_output(&exti_ll_get_gpio_ll(&dev->exti_sda), bit);
            } else {
                gpio_ll_set_mode(&exti_ll_get_gpio_ll(&dev->exti_sda), GPIO_LL_MODE_INPUT);
            }
        }
    }
}
/**
 * @brief i2c从机 SDA管脚驱动服务函数 电平变化时调用
 * @param  dev
 * @note 外部中断 跳变沿触发
 */
static inline void i2c_slave_sda_service(i2c_slave_dev_t *dev) {
    if (gpio_ll_get_input(&exti_ll_get_gpio_ll(&dev->exti_sda))) {
        dev->__status |= I2C_SLAVE_STATUS_SDA;
        if (dev->__status & I2C_SLAVE_STATUS_SCL) {
            dev->__status &= ~I2C_SLAVE_STATUS_START; // 清除起始信号
            if (dev->__status & I2C_SLAVE_STATUS_ACCEPT) {
                dev->__status &= ~I2C_SLAVE_STATUS_ACCEPT;
                if ((dev->transfer_addr & 0x01) == 0) {
                    i2c_slave_transfer_data_t receive_data = {
                        .data = dev->receive_buf,
                        .len = dev->transfer_len,
                    };
                    dev->receive_cb(dev, &receive_data, dev->arg);
                }
            }
        }
    } else {
        dev->__status &= ~I2C_SLAVE_STATUS_SDA;
        if (dev->__status & I2C_SLAVE_STATUS_SCL) {
            dev->__status |= I2C_SLAVE_STATUS_START; // 设置起始信号
            dev->__status &= ~I2C_SLAVE_STATUS_ACK;
            dev->transfer_cnt = 0;
            // 没有发停止信号，但重新开始了，上次是写入数据，就调用接收事件函数
            if (dev->__status & I2C_SLAVE_STATUS_ACCEPT) {
                dev->__status &= ~I2C_SLAVE_STATUS_ACCEPT;
                if ((dev->transfer_addr & 0x01) == 0) {
                    i2c_slave_transfer_data_t receive_data = {
                        .data = dev->receive_buf,
                        .len = dev->transfer_len,
                    };
                    dev->receive_cb(dev, &receive_data, dev->arg);
                }
            }
            dev->transfer_len = 0;
        }
    }
}


/**********************************Private Functions*******************************/

static inline void i2c_slave_ack(i2c_slave_dev_t *dev) {
    gpio_ll_set_mode(&exti_ll_get_gpio_ll(&dev->exti_sda), GPIO_LL_MODE_OUTPUT);
    gpio_ll_set_output(&exti_ll_get_gpio_ll(&dev->exti_sda), 0);
}

static inline int i2c_slave_wait_ack(i2c_slave_dev_t *dev) {
    return gpio_ll_get_input(&exti_ll_get_gpio_ll(&dev->exti_sda));
}

static void exti_scl_cb(exti_ll_t *exti) {
    i2c_slave_scl_service(exti_ll_get_usr_arg(exti));
}

static void exti_sda_cb(exti_ll_t *exti) {
    i2c_slave_sda_service(exti_ll_get_usr_arg(exti));
}


/* [] END OF FILE */
